package com.yifang.yibingproject.config;

import com.alibaba.fastjson.JSONObject;
import com.yifang.yibingproject.config.filter.JwtFilter;
import com.yifang.yibingproject.config.filter.RequestFilter;
import com.yifang.yibingproject.entity.SysUserEntity;
import com.yifang.yibingproject.service.impl.SysUserServiceImpl;
import com.yifang.yibingproject.utils.JwtUtils;
import com.yifang.yibingproject.utils.RedisUtils;
import com.yifang.yibingproject.utils.ResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Configuration
@EnableWebSecurity // 开启Security安全拦截
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启权限注解拦截
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RequestFilter requestFilter;

//    @Autowired
//    private VerifyCodeFilter verifyCodeFilter;

    @Autowired
    private JwtFilter jwtFilter;

    @Autowired
    private SysUserServiceImpl sysUserServiceImpl;

    @Value("${dailubao.token.expiration:12}")
    private Integer tokenExpiration;

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/favicon.ico", "/swagger*/**", "/webjars/**", "/v2/api-docs", "/doc.md");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /*图片验证码过滤器设置在密码验证之前*/
        http.addFilterBefore(jwtFilter, LogoutFilter.class);
        http.addFilterBefore(requestFilter, JwtFilter.class);

        http.authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll() // 允许所有跨域Options请求
                .antMatchers("/sms/sendVerifyCode").permitAll() // 短信放行
                .antMatchers("/file/**").permitAll() // 上传放行

                .antMatchers("/address/**").permitAll() // 地址放行
                .antMatchers("/transFeeTpl/add").permitAll()
                .antMatchers("/api/user/login").permitAll()
                .antMatchers("/api/**").permitAll()
                .antMatchers("/page/index").permitAll() //登录页面放行
                .antMatchers("/s", "/login.html", "/user/login", "/logout", "/verifyCode/code-image.jpg").permitAll()
                .anyRequest().authenticated()
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/login.html")
                .logoutSuccessHandler((HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) -> {
                    httpServletResponse.setStatus(HttpStatus.OK.value());
                    httpServletResponse.setContentType("application/json;charset=utf-8");
                    if (authentication != null) {
                        String userId = (String) authentication.getPrincipal();
                        RedisUtils.delete(JwtUtils.getRedisTokenKey(userId)); // 删除用户token
                        RedisUtils.delete(JwtUtils.getRedisAuthKey(userId)); // 删除用户权限
                        log.info("=====删除Redis Token======" + userId);
                    }
                    try {
                        httpServletResponse.getWriter().write(JSONObject.toJSONString(ResultUtils.success()));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                })
                .and().formLogin().loginPage("/login.html").loginProcessingUrl("/user/login")
                /*自定义登录成功处理，返回token值*/
                .successHandler((HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) -> {
                    log.info("用户为====>" + httpServletRequest.getParameter("username") + "登录成功");
                    httpServletResponse.setContentType("application/json;charset=utf-8");
                    /*获取用户权限信息*/
                    SysUserEntity user = (SysUserEntity) authentication.getPrincipal();
                    Map<String, Object> userMap = new HashMap<>();
                    userMap.put("userId", user.getId());
                    userMap.put("loginDevice", "pc"); // 登录设备电脑
                    String token = JwtUtils.generateToken(userMap);
                    /*存储redis并设置了过期时间*/
                    RedisUtils.setStr(JwtUtils.getRedisTokenKey(user.getId()), token, Duration.ofHours(tokenExpiration));

                    // 存储用户权限
                    Set<String> authSet = user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
                    String authStr = String.join(",", authSet);
                    RedisUtils.setStr(JwtUtils.getRedisAuthKey(user.getId()), authStr, Duration.ofHours(tokenExpiration));
                    //redisTemplate.boundValueOps(JwtUtils.getRedisAuthKey(user.getId())).set(authStr, tokenExpiration, TimeUnit.HOURS);
                    /*认证信息写入header*/
                    httpServletResponse.setHeader("Authorization", token);
                    log.info("生成TOKEN=" + token);
                    httpServletResponse.getWriter().write(JSONObject.toJSONString(ResultUtils.data(token)));
                })
                /*登录失败处理*/
                .failureHandler((HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) -> {
                    log.info("用户为====>" + request.getParameter("username") + "登录失败");
                    response.setStatus(HttpStatus.OK.value());
                    response.setContentType("application/json;charset=utf-8");
                    response.getWriter().write(JSONObject.toJSONString(ResultUtils.fail(-8, "用户名或密码错误")));
                })
                /*无权限访问处理*/
                .and().exceptionHandling()
                .accessDeniedHandler((HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) -> {
                    response.setStatus(HttpStatus.OK.value());
                    response.setContentType("application/json;charset=utf-8");
                    response.getWriter().write(JSONObject.toJSONString(ResultUtils.fail(-7, "权限不足")));
                })
                /*匿名用户访问无权限资源时的异常*/
                .and().exceptionHandling()
                .authenticationEntryPoint((HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) -> {
//                    throw new NotLoginException();
                    response.setStatus(HttpStatus.OK.value());
                    response.setContentType("application/json;charset=utf-8");
                    response.getWriter().write(JSONObject.toJSONString(ResultUtils.fail(-4, "未登录")));
                })
                .and().authorizeRequests()
                /*基于token，所以不需要session*/
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().headers().frameOptions().disable() // 允许iframe内嵌跨域请求
                .and().cors() // 开启跨域，会查找corsFilter
                /*由于使用的是jwt，这里不需要csrf防护并且禁用缓存*/
                .and().csrf().disable().headers().cacheControl();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(sysUserServiceImpl).passwordEncoder(passwordEncoder());
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 跨域过滤器配置
     */
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}
